/***************************************************************************
 *cr
 *cr            (C) Copyright 1995-2019 The Board of Trustees of the
 *cr                        University of Illinois
 *cr                         All Rights Reserved
 *cr
 ***************************************************************************/

/***************************************************************************
 * RCS INFORMATION:
 *
 *      $RCSfile: SelectionBuilder.C,v $
 *      $Author: johns $        $Locker:  $             $State: Exp $
 *      $Revision: 1.30 $       $Date: 2019/01/17 21:21:01 $
 *
 ***************************************************************************
 * DESCRIPTION:
 *  generated by Fast Light User Interface Designer (fluid) version 1.0011
 ***************************************************************************/

#include "SelectionBuilder.h"
#include "Molecule.h"
#include "SymbolTable.h"
#include "ParseTree.h"
#include "FL/Fl.H"
#include "FL/forms.H"

extern "C" int vmd_int_compare(const void *x, const void *y) {
   return *((const int *) x) - *((const int *) y);
}

extern "C" int vmd_double_compare(const void *x, const void *y) {
   double delta = *((const double *) x) - *((const double *) y);
   if (delta < 0) {
      return -1;
   }
   if (delta == 0) {
      return 0;
   }
   return 1;
}

extern "C" int vmd_string_compare(const void *x, const void *y) {
   return strcmp( *((const char **) x), *((const char **) y));
}

static void vmdsort_int(int *intlist, int num) {
  qsort(intlist, num, sizeof(int), vmd_int_compare);
}

static void vmdsort_double(double *dlist, int num) {
  qsort(dlist, num, sizeof(double), vmd_double_compare);
}

static void vmdsort_string(const char **jlist, int num) {
  qsort(jlist, num, sizeof(const char *), vmd_string_compare);
}


SelectionBuilder::SelectionBuilder(int xpos, int ypos, GraphicsFltkMenu *m, 
                                   Fl_Input *input, SymbolTable *sym) 
: Fl_Group(xpos, ypos, 300, 330, "Selections") 
{
  menu = m;
  selectiontext = input;
  mol = NULL;
  table = sym;
  goto_end = 0;

    applybutton = new Fl_Button(xpos+215, ypos+60, 70, 25, "Apply");
#if defined(VMDMENU_WINDOW)
    applybutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    applybutton->callback(apply_cb, this);

    resetbutton = new Fl_Button(xpos+215, ypos+85, 70, 25, "Reset");
#if defined(VMDMENU_WINDOW)
    resetbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    resetbutton->callback(reset_cb, this);
    { Fl_Browser* o = keywordbrowser = new Fl_Hold_Browser(xpos+15, ypos+180, 135, 150, "Keyword");
      o->color(VMDMENU_BROWSER_BG);
      o->selection_color(VMDMENU_BROWSER_SEL);
      o->align(FL_ALIGN_TOP);
      o->callback(keyword_cb, this);
    }
    { Fl_Browser* o = valuebrowser = new Fl_Hold_Browser(xpos+165, ypos+180, 135, 150, "Value");
      o->color(VMDMENU_BROWSER_BG);
      o->selection_color(VMDMENU_BROWSER_SEL);
      o->align(FL_ALIGN_TOP);
      o->callback(value_cb, this);
    }
    andbutton = new Fl_Button(xpos+205, ypos+25, 35, 25, "and");
#if defined(VMDMENU_WINDOW)
    andbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    andbutton->callback(and_cb, this);
    orbutton = new Fl_Button(xpos+240, ypos+25, 25, 25, "or");
#if defined(VMDMENU_WINDOW)
    orbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    orbutton->callback(or_cb, this);
    notbutton = new Fl_Button(xpos+265, ypos+25, 35, 25, "not");
#if defined(VMDMENU_WINDOW)
    notbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    notbutton->callback(not_cb, this);
    macrobrowser = new Fl_Hold_Browser(xpos+15, ypos+25, 180, 85, "Singlewords");
    macrobrowser->color(VMDMENU_BROWSER_BG, VMDMENU_BROWSER_SEL);
    macrobrowser->align(FL_ALIGN_TOP);
    macrobrowser->callback(macrobrowser_cb, this);

    macrooutput = new Fl_Output(xpos+15, ypos+130, 285, 25, "Macro definition:");
    macrooutput->align(FL_ALIGN_TOP | FL_ALIGN_LEFT);
    macrooutput->selection_color(VMDMENU_VALUE_SEL);
    // leave deactivated until it's possible to edit macros from the GUI
    macrooutput->deactivate();
    end();

  for (int i=0; i<table->fctns.num(); i++) {
    if (table->fctns.data(i)->is_a == SymbolTableElement::KEYWORD)
      keywordbrowser->add(table->fctns.name(i));
  }
}

void SelectionBuilder::update_macrobrowser() {
  macrobrowser->clear();
  for (int i=0; i<table->fctns.num(); i++) {
    if (table->fctns.data(i)->is_a == SymbolTableElement::SINGLEWORD) {
      const char *name = table->fctns.name(i);
      // macros are never removed, just renamed to ""
      if (!strlen(name)) continue;
      const char *macro = table->get_custom_singleword(name);
      if (macro) {
        macrobrowser->add(name);
        // XXX add to text input
      } else {
        JString buf("@i");
        buf += name;
        macrobrowser->add((const char *)buf);
      }
    } 
  } 
}
  
void SelectionBuilder::and_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  palette->append_text("and ");
}

void SelectionBuilder::or_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  palette->append_text("or ");
}

void SelectionBuilder::not_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  palette->append_text("not ");
}

void SelectionBuilder::apply_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  // Try to parse the text now, to make sure it's valid.  Parsing should
  // be very fast, not nearly so slow as actually finding the atoms.
  const char *seltext = palette->selectiontext->value();
  ParseTree *tree = palette->table->parse(seltext);
  if (tree) {
    delete tree;
    palette->menu->update_selection(seltext);
  } else {
    fl_message("Unable to parse this atom selection.");
  }
}

void SelectionBuilder::reset_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  palette->menu->update_selection(NULL);
}

void SelectionBuilder::macrobrowser_cb(Fl_Widget *, void *v) {
  SelectionBuilder *self = (SelectionBuilder *)v;

  int line = self->macrobrowser->value();
  if (!line) {
    self->macrooutput->value(NULL);
    return;
  }
  const char *name = self->macrobrowser->text(line);
  const char *macro = self->table->get_custom_singleword(name);
  if (macro) {
    // leave deactivated until it's possible to edit macros from the GUI
    //self->macrooutput->activate();
    self->macrooutput->value(macro);
  } else {
    self->macrooutput->value(name+2);  // offset by two because of italics
    // leave deactivated until it's possible to edit macros from the GUI
    //self->macrooutput->deactivate();
  }

  if (Fl::event_clicks()) {
    JString buf(self->macrooutput->value());
    buf += " ";
    self->append_text((const char *)buf);
    Fl::event_clicks(-1);
  }
}

void SelectionBuilder::value_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  if (Fl::event_clicks()) {
    int line = palette->valuebrowser->value();
    if (!line) return;
    JString buf(palette->valuebrowser->text(line));
    buf += " ";
    //palette->selectiontext->value((const char *)buf);  
    palette->append_text((const char *)buf); 
    Fl::event_clicks(-1);
  }
}
 
void SelectionBuilder::keyword_cb(Fl_Widget *, void *v) {
  SelectionBuilder *palette = (SelectionBuilder *)v;
  if (Fl::event_clicks()) {
    // add keyword to selection text
    int line = palette->keywordbrowser->value();
    if (!line) return;
    JString buf(
      palette->keywordbrowser->text(line));
    buf += " ";
    palette->append_text(buf);
    Fl::event_clicks(-1);
    return;
  } 
    // just update the value browser
    palette->valuebrowser->clear();
    
    // get current name list selection, and check for a category with names
    const char *keyword = palette->keywordbrowser->text(
        palette->keywordbrowser->value());
    int list = palette->table->fctns.typecode(keyword);
    
    // only if a list has been selected, and is useful one, do we enter names
    // also, only need to fill browser if there is a molecule currently 
    if(palette->mol && list >= 0 && list < palette->table->fctns.num()) {
      // tell the atom selection commands to use the current molecule to 
      // get the required information.  The current frame doesn't matter for
      // our purposes here, so just use -1 (which means the current frame)
      // Since we're looking only at keywords, not singlewords, macro is NULL.
      atomsel_ctxt context(palette->table, palette->mol, -1, NULL);

       // get all the info from the item in the table
       // first see what it does
       SymbolTableElement *info = palette->table->fctns.data(list);
       // the only one that shows anything are the KEYWORDs
       if (info -> is_a == SymbolTableElement::KEYWORD) {
          int num = palette->mol->nAtoms;
          int *flgs = new int[num];
          int i;
          for (i=0; i<num; i++) {
             flgs[i] = 1;
          }

          // initialize a hash table for finding the unique entries
          hash_t hash;
          hash_init(&hash, num);

          // get the the into an array whose type matches the type of the
          // data.  That way we don't convert to string until the end 
          switch(info -> returns_a) { 
            case (SymbolTableElement::IS_INT):
            {
              int *data = new int[num];
              JString *strdata = new JString[num];
              info->keyword_int(&context, num, data, flgs);
              vmdsort_int(data, num);
              for (i=0; i<num; i++) {
                char buf[40];
                sprintf(buf, "%d", data[i]);
                strdata[i] = buf;
                if (hash_insert(&hash, (const char *)strdata[i], 0) == HASH_FAIL)
                  palette->valuebrowser->add(buf);
              }
              delete [] data;
              delete [] strdata;
              break; 
            }
            case (SymbolTableElement::IS_FLOAT):
            {
              double *data = new double[num];
              JString *strdata = new JString[num];
              info->keyword_double(&context, num, data, flgs);
              vmdsort_double(data, num);
              for (i=0; i<num; i++) {
                char buf[40];
                float tmp = (float)data[i];
                sprintf(buf, "%f", tmp);
                strdata[i] = buf;
                if (hash_insert(&hash, (const char *)strdata[i], 0) == HASH_FAIL)
                  palette->valuebrowser->add(buf);
              }
              delete [] data;
              delete [] strdata;
              break;
            }
            case (SymbolTableElement::IS_STRING):
            {
              const char **data = new const char *[num];
              const char **hashdata = new const char *[num];
              int j=0;

              // XXX crashes can occur here when only coords are loaded
              // and "structure" is displayed in the selection builder.
              info->keyword_string(&context, num, data, flgs);

              for (i=0; i<num; i++) {
                // XXX prevent crashes when no atom names are available
                if (data[i] != NULL) {
                  if (hash_insert(&hash, data[i], 0) == HASH_FAIL)
                    hashdata[j++] = data[i];
                }  
              }

              vmdsort_string(hashdata, j);
              for (i=0; i<j; i++) {
                  palette->valuebrowser->add(hashdata[i]);
              }
              delete [] data;
              delete [] hashdata;
            }
            default: ;
          }
          hash_destroy(&hash);
          delete [] flgs;
       }
    }
}

void SelectionBuilder::append_text(const char *s) {
  // If there's selected text, replace it.  Otherwise, just append at the end
  selectiontext->replace(selectiontext->position(), selectiontext->mark(), s);

  if (goto_end) {
    selectiontext->position(selectiontext->size());
    goto_end = 0;
  }
}

void SelectionBuilder::use_molecule(Molecule *m) {
  mol = m;
  keyword_cb(keywordbrowser, this);
}

void SelectionBuilder::set_selection(const char *s) {
  selectiontext->value(s);
  selectiontext->position(selectiontext->size());
}
